﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using EntryEngine;
using EntryEngine.Network;
using Server.Impl;
using Server;


/// <summary>上传/下载文件通用</summary>
public static class _FILE
{
    /// <summary>本地资源访问的路径，例如 https://eecode.top/</summary>
    public static string AccessURL = "http://localhost:3000/";

    public const string NATIVE_UPLOAD_PATH = "temp/";

    /// <summary>检查是否存在文件夹，没有则创建</summary>
    public static void CheckPath(string path)
    {
        if (!string.IsNullOrEmpty(path) && !Directory.Exists(path))
            Directory.CreateDirectory(path);
    }
    /// <summary>检查是否存在文件的文件夹，没有则创建</summary>
    public static void CheckFilePath(string nativeFileName)
    {
        CheckPath(Path.GetDirectoryName(nativeFileName));
    }
    public static void DeleteFile(string key)
    {
        _LOG.Info("删除本地文件：{0}", key);
        if (File.Exists(key)) File.Delete(key);
    }
    public static void DeleteFile(string[] keys)
    {
        if (keys == null) return;
        for (int i = 0; i < keys.Length; i++)
            DeleteFile(keys[i]);
    }
    public static void DeleteDirectory(string relativePath)
    {
        _LOG.Info("删除本地目录：{0}", relativePath);
        if (Directory.Exists(relativePath))
            Directory.Delete(relativePath, true);
    }

    /// <summary>在修改上传的图片时，若是完整URL则不需要修改</summary>
    public static bool IsFullURL(this string url)
    {
        if (string.IsNullOrEmpty(url))
            return false;

        if (!string.IsNullOrEmpty(AccessURL))
            return url.StartsWith(AccessURL);

        // 网站和图片属于同一个源的情况下使用相对路径
        return File.Exists(url);
    }
    /// <summary>文件相对路径改为完整路径</summary>
    public static string ResolvePath(this string file)
    {
        if (string.IsNullOrEmpty(file))
            return string.Empty;
        else
            return AccessURL + file;
    }
    public static void ResolvePath(ref string file)
    {
        file = ResolvePath(file);
    }
    /// <summary>文件相对路径改为完整路径</summary>
    public static string[] ResolveImage(this string[] files)
    {
        if (files == null)
            files = new string[0];
        for (int i = 0; i < files.Length; i++)
            ResolvePath(ref files[i]);
        return files;
    }
    public static void CheckFileTypeCanEmpty(this string filename, params string[] suffix)
    {
        if (!string.IsNullOrEmpty(filename))
            CheckFileType(filename, suffix);
    }
    public static void CheckFileTypeCanEmpty(this string[] filenames, params string[] suffix)
    {
        if (filenames != null)
        {
            foreach (var item in filenames)
            {
                CheckFileType(item, suffix);
            }
        }
    }
    /// <summary>检查文件类型</summary>
    /// <param name="suffix">包含'.'</param>
    public static void CheckFileType(this string filename, params string[] suffix)
    {
        string extension = Path.GetExtension(filename);
        "不支持的文件格式".Check(suffix.Length > 0 && !suffix.Any(s => s == extension));
    }

    /// <summary>
    /// <para>将上传的文件写入临时文件夹暂存，上传文件和提交表单分两步的接口使用</para>
    /// <para>提交表单将返回的文件名再传回来，使用 SaveUploadFile 正式保存文件</para>
    /// <para>上传和提交表单是一步走的使用 SubmitUploadFile 直接保存</para>
    /// </summary>
    /// <param name="file">上传的文件</param>
    /// <param name="suffix">是否保留文件后缀，提交表单后 SaveUploadFile 时可以不写文件后缀</param>
    /// <returns>返回临时文件名</returns>
    public static string WriteUploadFile(FileUpload file, bool suffix = true)
    {
        CheckPath(NATIVE_UPLOAD_PATH);
        string fileName = Guid.NewGuid().ToString("n");
        if (suffix)
        {
            string extension = Path.GetExtension(file.Filename);
            if (!string.IsNullOrEmpty(extension))
                fileName += extension;
        }
        file.SaveAs(NATIVE_UPLOAD_PATH + fileName);
        return fileName;
    }

    /// <summary>提交表单时伴随着上传文件时使用。保存上传的文件到目标位置</summary>
    /// <param name="file">上传的文件</param>
    /// <param name="targetFileName">保存的目标文件名，可以不带后缀自动取上传文件的后缀</param>
    /// <returns>包含后缀的目标文件名</returns>
    public static string SubmitUploadFile(FileUpload file, string targetFileName)
    {
        if (string.IsNullOrEmpty(targetFileName))
            throw new ArgumentException("保存的文件名不能为空");

        // 最后要保存的文件缺少后缀则自动取上传文件的后缀
        if (string.IsNullOrEmpty(Path.GetExtension(targetFileName)))
        {
            // 自动添加后缀
            string extension = Path.GetExtension(file.Filename);
            if (!string.IsNullOrEmpty(extension))
                targetFileName += extension;
        }

        CheckFilePath(targetFileName);
        _LOG.Debug("本地保存上传的文件：{0} - {1}", file.Filename, targetFileName);
        file.SaveAs(targetFileName);
        return targetFileName;
    }
    /// <summary>提交表单时伴随着上传文件时使用。保存上传的文件到目标位置</summary>
    /// <param name="file">上传的文件</param>
    /// <param name="dir">保存的目标文件夹</param>
    /// <param name="useUploadFileName">是否使用上传的文件名，谨慎使用，用户上传的文件名可修改。默认会随机一个 guid 最为文件名</param>
    public static string SubmitUploadFile(FileUpload file, string dir, bool useUploadFileName = false)
    {
        return SubmitUploadFile(file, Path.Combine(dir, useUploadFileName ? file.Filename : Guid.NewGuid().ToString("n")));
    }
    /// <summary>提交表单时伴随着上传文件时使用。保存上传的文件到目标位置</summary>
    /// <param name="file">上传的文件</param>
    /// <param name="dir">保存的目标文件夹</param>
    /// <param name="useUploadFileName">是否使用上传的文件名，谨慎使用，用户上传的文件名可修改。默认会随机一个 guid 最为文件名</param>
    public static string[] SubmitUploadFile(FileUpload[] files, string dir, bool useUploadFileName = false)
    {
        string[] results = new string[files.Length];
        for (int i = 0; i < results.Length; i++)
            results[i] = SubmitUploadFile(files[i], Path.Combine(dir, useUploadFileName ? files[i].Filename : Guid.NewGuid().ToString("n")));
        return results;
    }

    /// <summary>拷贝uploadFile到targetFileName，删除uploadFile的文件</summary>
    /// <param name="uploadFile">WriteUploadFile返回的路径</param>
    /// <param name="targetFileName">要保存的目标路径</param>
    private static void SaveUploadFile(string uploadFile, ref string targetFileName)
    {
        if (string.IsNullOrEmpty(Path.GetFileNameWithoutExtension(targetFileName)))
            throw new ArgumentException("保存的文件名不能为空");

        // 最后要保存的文件缺少后缀则自动取上传文件的后缀
        if (string.IsNullOrEmpty(Path.GetExtension(targetFileName)))
        {
            // 自动添加后缀
            string extension = Path.GetExtension(uploadFile);
            if (!string.IsNullOrEmpty(extension))
                targetFileName += extension;
        }

        if (uploadFile != targetFileName)
        {
            CheckFilePath(targetFileName);
            // 写入本地
            _LOG.Debug("本地保存上传的文件：{0} - {1}", uploadFile, targetFileName);
            File.Copy(uploadFile, targetFileName, true);
            if (File.Exists(uploadFile))
                File.Delete(uploadFile);
        }
    }
    /// <summary>提交带有上传文件的表单时使用。确保替换最新上传的文件，删除已经被替换掉的旧文件</summary>
    /// <param name="upload">本次上传的图片路径，有可能没有变化，有变化则变为最新的路径</param>
    /// <param name="oldFile">之前已经上传过的文件路径</param>
    /// <param name="newFile">本次要保存的目标路径</param>
    /// <example>
    /// bool IUser.Modify(T_GOODS data) {
    ///     var old = _DB._T_GOODS.Select(data.ID);
    ///     _FILE.SaveUploadFile(ref data.Cover, old?.Cover, $"goods/cover/{data.ID}", false);
    ///     _DB._T_GOODS.Update(data);
    ///     return true;
    /// }
    /// </example>
    public static void SaveUploadFile(ref string upload, string oldFile, string newFile, bool isDeleteOld = true)
    {
        bool isOldEmpty = string.IsNullOrEmpty(oldFile);
        bool isNewEmpty = string.IsNullOrEmpty(upload);

        if (!isNewEmpty)
        {
            // 上传新文件 | 新旧文件不一样替换文件
            if (oldFile != upload)
            {
                // 保存新文件
                SaveUploadFile(NATIVE_UPLOAD_PATH + upload, ref newFile);
                upload = newFile;
            }
            else
                return;
        }
        if (isDeleteOld && !isOldEmpty)
        {
            // 新旧文件不一样，删除旧文件
            DeleteFile(oldFile);
        }
    }
    //public static void SaveUploadFile(ref string upload, string newFile)
    //{
    //    // 上传新文件
    //    SaveUploadFile(NATIVE_UPLOAD_PATH + upload, ref newFile);
    //    upload = newFile;
    //}
    /// <summary>提交带有上传文件的表单时使用。确保替换最新上传的文件，删除已经被替换掉的旧文件</summary>
    /// <param name="uploads">本次上传的结果，如果是修改表单则可能夹杂着之前的文件。最终会变为最新的结果，结果不会为 null 至少是 0 长数组</param>
    /// <param name="oldFiles">如果是修改表单，则这是之前的结果。如果是新增，则保持 null 就好</param>
    /// <param name="newFiles">本次上传的结果要最终保存的路径。对于修改表单，本次上传没有变化的则不会采用 newFiles 的结果，相反 newFiles 的结果会改回成旧文件</param>
    /// <param name="isDeleteOld">对于已经不需要了的旧文件是否要删除</param>
    /// <exception cref="ArgumentException">新旧文件数组长度不一致</exception>
    /// <example>
    /// bool IUser.Modify(T_GOODS data) {
    ///     var old = _DB._T_GOODS.Select(data.ID);
    ///     _FILE.SaveUploadFile(ref data.Images, old?.Images, data.Images?.Select(i => "goods/images" + i).ToArray(), true);
    ///     _DB._T_GOODS.Update(data);
    ///     return true;
    /// }
    /// </example>
    public static void SaveUploadFile(ref string[] uploads, string[] oldFiles, string[] newFiles, bool isDeleteOld = true)
    {
        if (oldFiles == null)
            oldFiles = new string[0];
        if (uploads == null)
            uploads = new string[0];
        if (newFiles == null)
            newFiles = new string[0];

        if (uploads.Length != newFiles.Length)
            throw new ArgumentException("上传的文件和相对应的文件名数组长度应该一样");

        // 需要删除的文件
        List<string> delete = new List<string>();

        // 文件已经不存在，需要删除
        if (isDeleteOld)
            for (int i = 0; i < oldFiles.Length; i++)
                if (!uploads.Contains(oldFiles[i]))
                    DeleteFile(oldFiles[i]);

        // 新文件，需要重新上传
        for (int i = 0; i < uploads.Length; i++)
        {
            int index = oldFiles.IndexOf(uploads[i]);
            if (index == -1)
                // 新文件
                SaveUploadFile(NATIVE_UPLOAD_PATH + uploads[i], ref newFiles[i]);
            //_LOG.Debug("新文件：{0}", newFiles[i]);
            else
                // 没有变化的文件
                newFiles[i] = oldFiles[index];
            //_LOG.Debug("没有变化的文件：{0}", uploads[i]);
        }

        uploads = newFiles;
    }
    /// <summary>提交带有上传文件的表单时使用。确保替换最新上传的文件，删除已经被替换掉的旧文件</summary>
    /// <param name="uploads">本次上传的结果，如果是修改表单则可能夹杂着之前的文件。最终会变为最新的结果，结果不会为 null 至少是 0 长数组</param>
    /// <param name="oldFiles">如果是修改表单，则这是之前的结果。如果是新增，则保持 null 就好</param>
    /// <param name="dir">上传的结果要最终保存的目录</param>
    /// <param name="isDeleteOld">对于已经不需要了的旧文件是否要删除</param>
    public static void SaveUploadFile(ref string[] uploads, string[] oldFiles, string dir, bool isDeleteOld = true)
    {
        int len = uploads == null ? 0 : uploads.Length;
        string[] newFiles = new string[len];
        for (int i = 0; i < len; i++)
            newFiles[i] = dir + uploads[i];
        SaveUploadFile(ref uploads, oldFiles, newFiles, isDeleteOld);
    }
    //public static void SaveUploadFile(ref string[] uploads, string dir)
    //{
    //    int len = uploads == null ? 0 : uploads.Length;
    //    string[] newFiles = new string[len];
    //    for (int i = 0; i < len; i++)
    //    {
    //        newFiles[i] = dir + uploads[i];
    //        SaveUploadFile(NATIVE_UPLOAD_PATH + uploads[i], ref newFiles[i]);
    //        uploads[i] = newFiles[i];
    //    }
    //}
}

/// <summary>缓存一些通用状态</summary>
public static class _CACHE
{
    class Cache
    {
        public DateTime Expired;
        public object Data;
    }

    private static Dictionary<string, Cache> cache = new Dictionary<string, Cache>();

    /// <summary>缓存一个数据</summary>
    /// <param name="func"></param>
    /// <param name="key">数据唯一 key，不指定则默认为泛型 T 的全名，建议指定</param>
    /// <param name="expiredSecond">数据过期时间，单位秒，默认 60 秒</param>
    /// <param name="force">强制刷新缓存，默认不强制</param>
    public static T CacheData<T>(Func<T> func, string key = null, int expiredSecond = 60, bool force = false)
    {
        if (string.IsNullOrEmpty(key))
            key = typeof(T).FullName;

        if (cache.TryGetValue(key, out Cache cacheData) && !force && DateTime.Now < cacheData.Expired)
            return (T)cacheData.Data;

        var newData = func();
        if (cacheData == null)
        {
            cacheData = new Cache();
            cache.Add(key, cacheData);
        }
        cacheData.Expired = DateTime.Now + TimeSpan.FromSeconds(expiredSecond);
        cacheData.Data = newData;
        return newData;
    }
    public static bool CacheClear(string key)
    {
        return cache.Remove(key);
    }
}

/// <summary>需要系统自动维护的内容</summary>
public class _AUTO : ImplBase
{
    static DateTime lastRefreshTime;

    static ICoroutine Refresh()
    {
        lastRefreshTime = DateTime.Now;
        return _DB._DAO.ExecuteAsync(db =>
        {
            _LOG.Info("初始化自动操作信息");
        });
    }
    public static IEnumerable<ICoroutine> Update()
    {
        yield return Refresh();
        while (true)
        {
            GameTime time = GameTime.Time;
            if ((DateTime.Now - lastRefreshTime).TotalMinutes >= 30)
                yield return Refresh();

            yield return null;
        }
    }
}